package pl.msikora.android.lapmagic.view;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import pl.msikora.android.lapmagic.R;
import pl.msikora.android.lapmagic.database.LapMagicDatabaseHelper;
import pl.msikora.android.lapmagic.database.LapsTable;
import pl.msikora.android.lapmagic.database.PointsTable;
import pl.msikora.android.lapmagic.database.SessionsTable;
import pl.msikora.android.lapmagic.database.SplitsTable;
import pl.msikora.android.lapmagic.database.TracksTable;
import pl.msikora.android.lapmagic.view.overlay.PointOverlayItem;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.widget.Chronometer;
import android.widget.TextView;
import android.widget.Toast;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.maps.GeoPoint;

// TODO save every location after finished lap

/**
 * Class handling lap timing session.
 * 
 * @author Maciej Sikora
 *
 */
public class RunSessionActvity extends SherlockActivity
	implements LocationListener {

	private final String TAG = "RunSessionActvity";
	private final String LAP_TIME_FORMAT = "mm:ss.SSS";
	private final String SPEED_FORMAT = "%d km/h";
	
	private final long minTime = 0;
	private final float minDistance = 1.0f;
	private final float kmhToMsRatio = 3.6f;
	private final int startIndex = 0;
	
	private LocationManager locationManager;
	private String provider;
	private long trackId;
	private long sessionId = -1;
	private ArrayList<PointOverlayItem> points =
			new ArrayList<PointOverlayItem>();
	private PointOverlayItem matched = null;
	private long[] splitTimes = null;
	private int lapNo = 0;
	private long bestLap = Long.MAX_VALUE;
	private long currentLap;
	private float topSpeed = 0.0f;
	private Chronometer lapChronometer;
	private TextView bestLapView;
	private TextView lapNoView;
	private TextView speedView;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.run_session);
		lapChronometer = (Chronometer) findViewById(R.id.lapChronometer);
		bestLapView = (TextView) findViewById(R.id.bestLap);
		lapNoView = (TextView) findViewById(R.id.lapNo);
		speedView = (TextView) findViewById(R.id.speed);
		ActionBar actionBar = getSupportActionBar();
		actionBar.setTitle(R.string.run_session_title);
		
		locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        boolean enabled = locationManager.isProviderEnabled(
        		LocationManager.GPS_PROVIDER);
        if (!enabled) {
        	Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        	startActivity(intent);
        }
        
        Criteria criteria = new Criteria();
        criteria.setSpeedRequired(true);
        provider = locationManager.getBestProvider(criteria, false);
        Log.d(TAG, "Set the provider to " + provider);

		trackId = getIntent().getExtras().getLong("id");
		Log.d(TAG, "Retrieved track id " + trackId);

		setPoints();
	}
	
	@Override
	public void onResume() {
		super.onResume();
		Log.d(TAG, "onResume fired");
		locationManager.requestLocationUpdates(provider, minTime, minDistance,
				this);
		Location lastLocation = locationManager.getLastKnownLocation(provider);
		if (lastLocation != null)
			onLocationChanged(lastLocation);
	}
	
	@Override
	public void onDestroy() {
		Log.d(TAG, "onDestroy fired");
		locationManager.removeUpdates(this);
		super.onDestroy();
	}

	@Override
	public void onBackPressed() {
		// do nothing
	}
	
	@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getSupportMenuInflater().inflate(R.menu.run_session, menu);
        return true;
    }

	@Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
    	case R.id.stopSession:
    		AlertDialog.Builder builder = new AlertDialog.Builder(this);
    		builder.setTitle(R.string.alert_dialog_stop_title)
    			.setMessage(R.string.alert_dialog_stop_session)
    			.setPositiveButton(R.string.alert_dialog_yes,
    					new OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
			    		Log.d(TAG, "stopping session");
			    		finish();					
					}
				})
				.setNegativeButton(R.string.alert_dialog_cancel,
						new OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.cancel();
					}
				});
    		builder.create().show();
    		return true;
    	}
    	return super.onOptionsItemSelected(item);
    }

	@Override
	public void onLocationChanged(Location location) {
		double lat = location.getLatitude();
		double lng = location.getLongitude();
		float accuracy = location.getAccuracy();
		float speed = location.getSpeed() * kmhToMsRatio;
		Log.d(TAG, "location changed to latitude = " + lat + ", longitude = "
				+ lng + ", accuracy = " + accuracy + " m, speed = " + speed
				+ " km/h");
		
		speedView.setText(String.format(SPEED_FORMAT, (int) speed));
		if (speed > topSpeed)
			topSpeed = speed;
		
		for (int i = 0; i < points.size(); ++i) {
			PointOverlayItem point = points.get(i);
			float toPoint = location.distanceTo(point.getLocation());
			Log.d(TAG, "distance to " + point.getName() + " = " + toPoint
					+ " m, lap no. = " + lapNo);

			if (toPoint < point.getRadius()) {
				if (matched == null || matched != point) {
					matched = point;
					Log.d(TAG, "matched " + point.getName());

					if (i == startIndex) 
						newLap();
					else if (lapNo > 0)
						newSplitTime(i);

					return;
				}
			}
			else if (matched != null && matched == point) {
				Log.d(TAG, "got away from " + point.getName());
				matched = null;			
			}
		}
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		Log.d(TAG, "provider " + provider + " status changed to "
		+ (status == 0 ? "out of service" : (status == 1 ?
		"temporary unavailable" : (status == 2 ? "available" : "unknown"))));
	}

	@Override
	public void onProviderEnabled(String provider) {
		Log.d(TAG, "enabled provider " + provider);
	}

	@Override
	public void onProviderDisabled(String provider) {
		Log.d(TAG, "disabled provider " + provider);
	}
	
	public void setPoints() {
		String startType = getString(R.string.track_detail_point_type_start);

		SQLiteDatabase db = LapMagicDatabaseHelper.getInstance(this)
				.getReadableDatabase();
		Cursor pointsCursor = db.query(PointsTable.TABLE_POINTS, new String[] {
				PointsTable.COLUMN_ID, PointsTable.COLUMN_LATITUDE,
				PointsTable.COLUMN_LONGITUDE, PointsTable.COLUMN_NAME,
				PointsTable.COLUMN_TYPE, PointsTable.COLUMN_RADIUS },
				PointsTable.COLUMN_TRACK_ID + " = " + trackId, null, null,
				null, null);
		
		for (pointsCursor.moveToFirst(); !pointsCursor.isAfterLast(); 
				pointsCursor.moveToNext()) {

			int latitude = pointsCursor.getInt(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_LATITUDE));
			int longitude = pointsCursor.getInt(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_LONGITUDE));
			String name = pointsCursor.getString(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_NAME));
			String type = pointsCursor.getString(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_TYPE));
			long point_id = pointsCursor.getLong(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_ID));
			long radius = pointsCursor.getLong(pointsCursor
					.getColumnIndexOrThrow(PointsTable.COLUMN_RADIUS));

			GeoPoint geoPoint = new GeoPoint(latitude, longitude);

			PointOverlayItem point = new PointOverlayItem(geoPoint, name,
					type, point_id, radius);
			point.setLocation(provider);
			
			if (type.equals(startType)) {
				
				// start point at specific position
				points.add(startIndex, point);
				Log.d(TAG, "adding start point");
			}
			else { // split type
				points.add(point);
				Log.d(TAG, "adding split point");
			}
			
			Log.d(TAG, "latitude = " + point.getLocation().getLatitude()
				+ ", longitude = " + point.getLocation().getLongitude()
				+ ", name = " + name + ", type = " + type + ", radius = "
				+ radius);
		}
		
		// there is no need for a slot for start point
		splitTimes = new long[points.size() - 1];
		clearSplitTimes();
		pointsCursor.close();
		
		Log.d(TAG, "after setting points, points = " + points + ", size = "
			+ points.size() + ", split times array = " + splitTimes
			+ ", size = " + splitTimes.length);
	}
		
	private void restartChronometer() {
		Log.d(TAG, "starting chronometer");
		lapChronometer.setBase(SystemClock.elapsedRealtime());
		lapChronometer.start();	
	}
	
	private long getChronometerTime() {
		return SystemClock.elapsedRealtime() - lapChronometer.getBase();
	}
	
	private String getFormattedTime(long milliseconds) {
		SimpleDateFormat formatter = new SimpleDateFormat(LAP_TIME_FORMAT);
		return formatter.format(new Date(milliseconds));
	}

	private void newLap() {
		SQLiteDatabase db = LapMagicDatabaseHelper.getInstance(this)
				.getWritableDatabase();
		
		if (lapNo > 0) {
			currentLap = getChronometerTime();
			restartChronometer();
			Toast.makeText(this, getString(R.string.run_session_lap_message)
				+ " " + lapNo + " - " + getFormattedTime(currentLap),
				Toast.LENGTH_SHORT).show();
			
			if (currentLap < bestLap)
				newBestLapTime(currentLap);

			(new SaveLapTask()).execute(db);
		}
		else {
			// start 1st lap and save the session
			restartChronometer();
			(new SaveSessionTask()).execute(db);
		}

		++lapNo;
		clearSplitTimes();
		topSpeed = 0;
		lapNoView.setText(String.valueOf(lapNo));
		Toast.makeText(this, getString(R.string.run_session_lap_message) + " "
			+ lapNo, Toast.LENGTH_LONG).show();
	}
	
	private void newBestLapTime(long lapTime) {
		bestLap = lapTime;
		String formattedBestLapTime = getFormattedTime(lapTime);
		bestLapView.setText(formattedBestLapTime);
		Toast.makeText(this, getString(R.string.run_session_new_best_lap) + " "
			+ formattedBestLapTime, Toast.LENGTH_SHORT).show();	
	}
	
	private void newSplitTime(int splitIndex) {
		long splitTime = getChronometerTime();
		
		// split time is 1 index behind to the corresponding point
		splitTimes[splitIndex - 1] = splitTime;
		String splitName = points.get(splitIndex).getName();
		Log.d(TAG, splitName + " time " + getFormattedTime(splitTime));
		Toast.makeText(this, splitName + " - " + getFormattedTime(splitTime),
			Toast.LENGTH_SHORT).show();
	}
	
	private void clearSplitTimes() {
		for (int i = 0; i < splitTimes.length; ++i)
			splitTimes[i] = 0;
	}
	
	private class SaveSessionTask extends AsyncTask<SQLiteDatabase, Void, Long> {

		@Override
		protected Long doInBackground(SQLiteDatabase... db) {
			
			// get track name - a default for session name
			Cursor trackCursor = db[0].query(TracksTable.TABLE_TRACKS, 
				new String[] { TracksTable.COLUMN_NAME }, TracksTable.COLUMN_ID
				+ " = " + trackId, null, null, null, null);
			trackCursor.moveToFirst();
			String trackName = trackCursor.getString(trackCursor
						.getColumnIndexOrThrow(TracksTable.COLUMN_NAME));
			trackCursor.close();
			
			ContentValues sessionValues = new ContentValues();
			sessionValues.put(SessionsTable.COLUMN_TRACK_ID, trackId);
			sessionValues.put(SessionsTable.COLUMN_NAME, trackName);
			
			Log.d(TAG, "inserting new session " + trackName + " in async task");
			Long newSessionId = db[0].insert(SessionsTable.TABLE_SESSIONS, null,
					sessionValues);
			return newSessionId;
		}
		
		@Override
		protected void onPostExecute(Long newSessionId) {
			// update session id in the UI thread
			Log.d(TAG, "setting session id = " + newSessionId
				+ " after async task execution");
			sessionId = newSessionId;
		}
		
	}
	
	private class SaveLapTask extends AsyncTask<SQLiteDatabase, Void, Void> {

		private int taskLapNo;
		private long taskLapTime;
		private float taskTopSpeed;
		private long[] taskSplitTimes;
		
		@Override
		protected void onPreExecute() {
			// set up lap data in the UI thread
			taskLapNo = lapNo;
			taskLapTime = currentLap;
			taskTopSpeed = topSpeed;
			taskSplitTimes = splitTimes.clone();
		}
		
		@Override
		protected Void doInBackground(SQLiteDatabase... db) {
			Log.d(TAG, "Saving lap data: Lap No. " + taskLapNo + ", lap time "
				+ getFormattedTime(taskLapTime) + ", top speed "
				+ taskTopSpeed + " in async task");
			
			ContentValues values = new ContentValues();
			values.put(LapsTable.COLUMN_SESSION_ID, sessionId);
			values.put(LapsTable.COLUMN_LAP_NO, taskLapNo);
			values.put(LapsTable.COLUMN_LAP_TIME, taskLapTime);
			values.put(LapsTable.COLUMN_TOP_SPEED, taskTopSpeed);
			
			db[0].insert(LapsTable.TABLE_LAPS, null, values);
			values.clear();
			
			values.put(SplitsTable.COLUMN_LAP_SESSION_ID, sessionId);
			values.put(SplitsTable.COLUMN_LAP_NO, taskLapNo);
			
			// skip the start point
			for (int i = startIndex + 1; i < points.size(); ++i) {
				String splitName = points.get(i).getName();
				long splitTime = taskSplitTimes[i - 1];
				Log.d(TAG, splitName + " time "
					+ getFormattedTime(splitTime));
				
				values.put(SplitsTable.COLUMN_SPLIT_NAME, splitName);
				values.put(SplitsTable.COLUMN_ON_SPLIT_TIME, splitTime);
				db[0].insert(SplitsTable.TABLE_SPLITS, null, values);
			}
			return null;
		}
		
	}
	
}
